package com.sxd.bill.rela.extractor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import java.util.Collections;
import java.util.Comparator;

import com.sxd.bill.common.enums.LabelNameEnum;
import com.sxd.bill.common.item.LabelItem;
import com.sxd.bill.common.item.TextLabelItem;
import com.sxd.bill.rela.item.CoreRelationItem;
import com.sxd.bill.rela.item.Template;
import com.sxd.bill.rela.item.TemplateResult;
import com.sxd.bill.rela.util.Joiner;

public class TemplateExtractor implements Extractable {
    protected List<Template> templates;

    public TemplateExtractor(List<Template> templates) {
        this.templates = templates;
    }

    public TemplateExtractor() {
        this.templates = new ArrayList<>();
    }

    public void addTemplate(String template) {
        this.templates.add(new Template(template));
    }

    public void addTemplates(List<String> templates) {
        for (String template : templates) {
            addTemplate(template);
        }
    }

    @Override
    public String toString() {
        return "TemplateExtractor(templates=[...])";
    }

    public void clearFilledTemplates() {
        for (Template template : templates) {
            template.reSetFilledTemplate();
        }
    }

    /**
     * 填充模板
     * 
     * @param label2express
     */
    private void fillTemplates(Map<LabelNameEnum, String> label2express) {
        for (Template template : templates) {
            template.fillTemplate(label2express);
        }
    }

    /**
     * 分别对每一个labelName下的多个实体entity进行拼接，拼接结果为正则表达式
     * 
     * @param label2entities
     * @return
     */
    private static Map<LabelNameEnum, String> generateEntityExpressionsForTemplate(
            Map<LabelNameEnum, List<String>> label2entities) {
        Map<LabelNameEnum, String> label2express = new HashMap<>();
        String tmpExp;
        for (Map.Entry<LabelNameEnum, List<String>> entry : label2entities.entrySet()) {
            List<String> entities = entry.getValue();
            LabelNameEnum labelName = entry.getKey();
            if (entry.getKey() == LabelNameEnum.DISCOUNTER) {
                List<String> newEntities = entities.stream().map(ent -> ent + "贴?").collect(Collectors.toList());
                tmpExp = Joiner.join(newEntities, "/?", "");
            } else if (labelName == LabelNameEnum.ACCEPTOR) {
                tmpExp = Joiner.join(entities, "[、,，\\s及和/\\+或者]{0,3}", "(?:大票|小票|大小票)?(?!贴)");
            } else if (labelName == LabelNameEnum.DUE_TIME) {
                tmpExp = Joiner.join(entities, "", "[\\.\\s、，及和至或者\\-\\+/]*");
            } else {
                tmpExp = Joiner.join(entities);
            }
            label2express.put(labelName, tmpExp);
        }
        return label2express;
    }

    /**
     * 根据 标签名labeName对 labelName,entity 的entity进行聚合，结果去重
     * 
     * @param textLabelItem
     * @return
     */
    private static Map<LabelNameEnum, List<String>> aggregateEntities(TextLabelItem textLabelItem) {
        Map<LabelNameEnum, Set<String>> label2entities = new HashMap<>();
        List<LabelItem> labelItems = textLabelItem.getLabelItems();
        for (LabelItem labelItem : labelItems) {
            LabelNameEnum labelName = labelItem.getLabelName();
            String entity = labelItem.getLabelEntity();
            if (!label2entities.containsKey(labelName)) {
                label2entities.put(labelName, new HashSet<>());
            }
            label2entities.get(labelName).add(entity);
        }
        // 由 Set转为List
        Map<LabelNameEnum, List<String>> label2entitiesList = label2entities.entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> new ArrayList<>(e.getValue())));
        return label2entitiesList;
    }

    /**
     * 遍历每个template进行关系抽取，并判定最佳的抽取结果
     * 
     * @param text
     * @return
     */
    protected List<Map<String, String>> extractFromTemplates(String text) {
        // 遍历每个Template抽取
        List<TemplateResult> results = new ArrayList<>();
        for (Template template : templates) {
            TemplateResult rst = template.extract(text);
            if (rst.size() > 0) {
                results.add(rst);
            }
        }
        if (results.size() > 0) {
            // 取抽取的元素数量最多的结果
            TemplateResult bestResult = Collections.max(results, Comparator.comparingInt(TemplateResult::size));
            return bestResult.getRelationMaps();
        } else {
            return TemplateResult.ofEmpty().getRelationMaps();
        }
    }

    /**
     * 对由模板抽取的结果中的承兑人、贴现人、票据期限这个3个标签字段分别进行分割，拆分成列表形式，其他抽取的字段则保持String形式不变
     * 
     * @param relationMaps
     * @param label2entities
     * @return
     */
    private static List<Map<String, Object>> splitEntities(List<Map<String, String>> relationMaps,
            Map<LabelNameEnum, List<String>> label2entities) {
        // 5.1 列举出List类型的标签: acceptor, discounter,duetime
        Set<LabelNameEnum> listTypeLabels = new HashSet<>(Arrays.asList(LabelNameEnum.ACCEPTOR,
                LabelNameEnum.DISCOUNTER, LabelNameEnum.DUE_TIME));
        // 5.2 对List类型的标签对应的实体构造正则表达式
        Map<String, String> label2expressForWrangling = label2entities.entrySet().stream()
                .filter(entry -> listTypeLabels.contains(entry.getKey()))
                .collect(Collectors.toMap(entry -> entry.getKey().getEnName(), entry -> Joiner.join(entry.getValue())));
        // 5.3 整理
        List<Map<String, Object>> newRelationMaps = new ArrayList<>();
        for (Map<String, String> relationMap : relationMaps) {
            Map<String, Object> newRelationMap = new HashMap<>();
            for (String labelName : relationMap.keySet()) {
                String entity = relationMap.get(labelName);
                if (label2expressForWrangling.containsKey(labelName)) {
                    String express = label2expressForWrangling.get(labelName);
                    Matcher m = Pattern.compile(express).matcher(entity);
                    List<String> entities = new ArrayList<>();
                    while (m.find()) {
                        entities.add(m.group());
                    }
                    newRelationMap.put(labelName, entities);
                } else {
                    newRelationMap.put(labelName, entity);
                }
            }
            newRelationMaps.add(newRelationMap);
        }
        return newRelationMaps;
    }

    /**
     * 关系抽取
     */
    @Override
    public List<CoreRelationItem> extract(TextLabelItem textLabelItem) {
        // 1. 按标签名称聚合标签实体
        Map<LabelNameEnum, List<String>> label2entities = aggregateEntities(textLabelItem);
        // 2. 生成“标签 -> 实体正则表达式”映射
        Map<LabelNameEnum, String> label2express = generateEntityExpressionsForTemplate(label2entities);
        // 3. 填充模板
        clearFilledTemplates();// 清空模板填充值
        fillTemplates(label2express);
        // 4. 模板抽取
        List<Map<String, String>> relationMaps = extractFromTemplates(textLabelItem.getText());
        // 5. 对抽取结果的acceptor, discounter,duetime 字段进行处理与格式转换(这三个字段为列表格式)
        List<Map<String, Object>> newRelationMaps = splitEntities(relationMaps, label2entities);
        // 6. 转为RelatonItem
        List<CoreRelationItem> relaItems = newRelationMaps.stream().map(item -> CoreRelationItem.fromMap(item))
                .collect(Collectors.toList());
        //
        relaItems.forEach(item -> item.setExtractingText(textLabelItem.getText()));
        return relaItems;
    }
}
